Skip to content

fix(server): Sequence destroy() after native teardown - #1513

Open
RandomByte wants to merge 1 commit into
mainfrom
fix/server-destroy-teardown-race
Open

fix(server): Sequence destroy() after native teardown#1513
RandomByte wants to merge 1 commit into
mainfrom
fix/server-destroy-teardown-race

Conversation

@RandomByte

@RandomByte RandomByte commented Aug 7, 2026

Copy link
Copy Markdown
Member

Fixes a unit test failing flakily in CI, most often on Node 22/24 on Windows, with test/lib/server/reinitialize.js exited with a non-zero exit code: 3221225477. That code is 0xC0000005, a native access violation on process teardown, not a test-assertion failure. The defect is in production teardown code, so the fix lands in Supervisor.js; the flaky reinitialize test is what surfaced it.

Root cause

Supervisor.destroy() passed the caller's callback to http.Server.close(), firing it on socket close. The BuildServer's teardown (the @parcel/watcher unsubscribe and the node:sqlite handle, which maps a 256 MB region of the database into memory) runs after that call and was not sequenced ahead of the callback, so server.close(resolve) resolved with SQLite still mid-close. The next test.serial opening a fresh DatabaseSync, or the worker exiting, then races the finalizing handle and raises an access violation on Windows (0xC0000005), surfacing as exited with a non-zero exit code: 3221225477 in the reinitialize suite.

Fix

Make destroy() a plain promise: start the socket close, then await the definition watcher and BuildServer teardown, and await the socket last. The promise resolves only once every native handle is closed. server.js adapts this to the existing close(callback) API and also returns the promise when no callback is passed, so callers can await it; a teardown error is passed to the callback (and rejects the promise) instead of being swallowed.

@RandomByte
RandomByte requested a review from a team August 7, 2026 09:57
@RandomByte
RandomByte marked this pull request as ready for review August 7, 2026 09:57
Comment thread packages/server/lib/serve/Supervisor.js Outdated
@RandomByte
RandomByte force-pushed the fix/server-destroy-teardown-race branch 2 times, most recently from 51f1c93 to 480feaf Compare August 11, 2026 09:41
@RandomByte
RandomByte requested review from a team and matz3 August 11, 2026 09:44

@matz3 matz3 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but the commit message / PR description of the "Fix" need to be updated, as they still describe the firing of the callback

Supervisor.destroy() passed the caller's callback to http.Server.close(),
firing it on socket close. The BuildServer's teardown (the @parcel/watcher
unsubscribe and the node:sqlite handle, which maps a 256 MB region of the
database into memory) runs after that call and was not sequenced ahead of
the callback, so server.close(resolve) resolved with SQLite still mid-close.
The next test.serial opening a fresh DatabaseSync, or the worker exiting,
then races the finalizing handle and raises an access violation on Windows
(0xC0000005), surfacing as `exited with a non-zero exit code: 3221225477`
in the reinitialize suite.

Make destroy() a plain promise: start the socket close, then await the
definition watcher and BuildServer teardown, and await the socket last.
The promise resolves only once every native handle is closed. server.js
adapts this to the existing close(callback) API and also returns the
promise when no callback is passed, so callers can await it; a teardown
error is passed to the callback (and rejects the promise) instead of
being swallowed.
@RandomByte RandomByte changed the title fix(server): Sequence destroy() callback after native teardown fix(server): Sequence destroy() after native teardown Aug 11, 2026
@RandomByte
RandomByte force-pushed the fix/server-destroy-teardown-race branch from 480feaf to 924f9df Compare August 11, 2026 15:48
@RandomByte

Copy link
Copy Markdown
Member Author

LGTM, but the commit message / PR description of the "Fix" need to be updated, as they still describe the firing of the callback

Thanks, I missed that. Updated the commit message and PR body

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants